﻿using System;
using System.Linq;
using System.Linq.Expressions;

namespace SemanticEx
{
	/// <summary>
	/// Convert a code string into an Expression.
	/// </summary>
	public class LambdaParser
	{
		#region Constructors

		protected LambdaParser(string input, IParameterScope scope = null)
		{
			Scope = scope ?? new GenericParameterScope();
			Lexer = new Lexer(input);
		}

		#endregion

		#region Properties

		protected IParameterScope Scope { get; set; }

		protected Lexer Lexer { get; set; }

		#endregion

		#region Methods

		public static dynamic Parse(string expression, IParameterScope scope = null)
		{
			return new DynamicInvocation(new LambdaParser(expression, scope).Evaluate());
		}

		protected virtual Delegate Evaluate()
		{
			Lexer.SkipWhiteSpace();

			ReadParameters();

			Lexer.SkipWhiteSpace();
			Lexer.Match("=>");
			Lexer.SkipWhiteSpace();
			var func = ReadExpression().Compile();
			Lexer.ValidateEOF();

			return func;
		}

		/// <summary>
		/// Read the lambda parameters.
		/// </summary>
		private void ReadParameters()
		{
			if (Lexer.IsMatch("("))
			{
				Lexer.Match("(");

				Scope.FindSymbol(Lexer.ReadIdentifier());
				while (!Lexer.IsMatch(")"))
				{
					Lexer.Match(",");
					Scope.FindSymbol(Lexer.ReadIdentifier());
				}
				Lexer.Match(")");
			}
			else
			{
				Scope.FindSymbol(Lexer.ReadIdentifier());
			}
		}

		protected LambdaExpression ReadExpression()
		{
			var body = ReadConditionalExpression();
			return Expression.Lambda(body, Scope.Parameters);
		}

		private Expression ReadConditionalExpression()
		{
			var expr = ReadBooleanExpression();
			if (Lexer.IsMatch("?"))
			{
				Lexer.Match("?");
				var ifTrue = ReadConditionalExpression();
				Lexer.Match(":");
				var ifFalse = ReadConditionalExpression();
				expr = expr.Condition(ifTrue, ifFalse);
			}
			return expr;
		}

		private Expression ReadBooleanExpression()
		{
			var expr = ReadEqualityExpression();
			while (Lexer.IsMatch("&&") || Lexer.IsMatch("||"))
			{
				if (Lexer.IsMatch("&&"))
				{
					Lexer.Match("&&");
					var rightExpr = ReadEqualityExpression();
					expr = expr.And(rightExpr);
				}
				else if (Lexer.IsMatch("||"))
				{
					Lexer.Match("||");
					var rightExpr = ReadEqualityExpression();
					expr = expr.Or(rightExpr);
				}
			}
			return expr;
		}

		private Expression ReadEqualityExpression()
		{
			var expr = ReadRelationalExpression();
			while (Lexer.IsMatch("==") || Lexer.IsMatch("!="))
			{
				if (Lexer.IsMatch("=="))
				{
					Lexer.Match("==");
					var rightExpr = ReadRelationalExpression();
					expr = expr.Equal(rightExpr);
				}
				else if (Lexer.IsMatch("!="))
				{
					Lexer.Match("!=");
					var rightExpr = ReadRelationalExpression();
					expr = expr.NotEqual(rightExpr);
				}
			}
			return expr;
		}

		private Expression ReadRelationalExpression()
		{
			var expr = ReadAdditionExpression();
			while (Lexer.IsMatch("<") || Lexer.IsMatch("<=") || Lexer.IsMatch("<") || Lexer.IsMatch(">="))
			{
				if (Lexer.IsMatch(">="))
				{
					Lexer.Match(">=");
					var rightExpr = ReadAdditionExpression();
					expr = expr.GreaterThanOrEqual(rightExpr);
				}
				else if (Lexer.IsMatch("<="))
				{
					Lexer.Match("<=");
					var rightExpr = ReadAdditionExpression();
					expr = expr.LessThanOrEqual(rightExpr);
				}
				else if (Lexer.IsMatch("<"))
				{
					Lexer.Match("<");
					var rightExpr = ReadAdditionExpression();
					expr = expr.GreaterThan(rightExpr);
				}
				else if (Lexer.IsMatch(">"))
				{
					Lexer.Match(">");
					var rightExpr = ReadAdditionExpression();
					expr = expr.LessThan(rightExpr);
				}
			}
			return expr;
		}

		private Expression ReadAdditionExpression()
		{
			var expr = ReadMultiplicationExpression();
			while (Lexer.IsMatch("+") || Lexer.IsMatch("-"))
			{
				if (Lexer.IsMatch("+"))
				{
					Lexer.Match("+");
					var rightExpr = ReadMultiplicationExpression();
					expr = expr.Add(rightExpr);
				}
				else if (Lexer.IsMatch("-"))
				{
					Lexer.Match("-");
					var rightExpr = ReadMultiplicationExpression();
					expr = expr.Subtract(rightExpr);
				}
			}
			return expr;
		}

		private Expression ReadMultiplicationExpression()
		{
			var expr = ReadPowerExpression();
			while (Lexer.IsMatch("*") || Lexer.IsMatch("/"))
			{
				if (Lexer.IsMatch("*"))
				{
					Lexer.Match("*");
					var rightExpr = ReadPowerExpression();
					expr = expr.Multiply(rightExpr);
				}
				else if (Lexer.IsMatch("/"))
				{
					Lexer.Match("/");
					var rightExpr = ReadPowerExpression();
					expr = expr.Divide(rightExpr);
				}
			}
			return expr;
		}

		private Expression ReadPowerExpression()
		{
			var expr = ReadUnaryExpression();
			while (Lexer.IsMatch("^"))
			{
				if (Lexer.IsMatch("^"))
				{
					Lexer.Match("^");
					var rightExpr = ReadUnaryExpression();
					expr = expr.Power(rightExpr);
				}
			}
			return expr;
		}

		private Expression ReadUnaryExpression()
		{
			Lexer.SkipWhiteSpace();
			if (Lexer.IsMatch("!"))
			{
				Lexer.Match("!");
				var expr = ReadPrimaryExpression();
				return expr.Not();
			}
			else if (Lexer.IsMatch("-"))
			{
				Lexer.Match("-");
				var expr = ReadPrimaryExpression();
				return expr.Negate();
			}
			return ReadPrimaryExpression();
		}

		private Expression ReadPrimaryExpression()
		{
			Lexer.SkipWhiteSpace();
			if (Lexer.IsMatch("("))
			{
				Lexer.Match("(");
				var expr = ReadConditionalExpression();

				Lexer.SkipWhiteSpace();
				Lexer.Match(")");
				return expr;
			}
			return ReadValue();
		}

		private Expression ReadValue()
		{
			Lexer.SkipWhiteSpace();
			return ReadNumber() ?? ReadString() ?? ReadIdentifier();
		}

		private Expression ReadNumber()
		{
			var value = Lexer.ReadNumber();
			if (value.HasValue)
			{
				if (value.Value.IsInteger())
				{
					return ((int)value).ToConstant();
				}
				return value.ToConstant();
			}
			return null;
		}

		private Expression ReadString()
		{
			var text = Lexer.ReadString();
			return (text == null) ? null : text.ToConstant();
		}

		private Expression ReadIdentifier()
		{
			var ident = Lexer.ReadIdentifier();
			if (ident == "null")
			{
				return ((object)null).ToConstant();
			}
			else if (ident == "true")
			{
				return true.ToConstant();
			}
			else if (ident == "false")
			{
				return false.ToConstant();
			}
			else
			{
				return ReadSymbol(ident);
			}
		}

		protected virtual Expression ReadSymbol(string ident)
		{
			if (!Scope.Parameters.Any(x => x.Name == ident))
			{
				Lexer.ThrowException("'{0}' is not defined.", ident);
			}
			return Scope.FindSymbol(ident);
		}

		#endregion
	}
}